package com.example.springsecurity05.config;

import com.example.springsecurity05.security.MyPersistentTokenBasedRememberMeServices;
import com.example.springsecurity05.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.sql.DataSource;
import java.util.Random;
import java.util.UUID;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final MyUserDetailService myUserDetailService;
    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;
    @Autowired
    private DataSource dataSource;

//    设置密码方式
    @Bean
    public PasswordEncoder passwordEncoder() {
//        指定加密的方式为bcrypt
//        return new BCryptPasswordEncoder();
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    public SecurityConfig(MyUserDetailService myUserDetailService) {
        this.myUserDetailService = myUserDetailService;
    }

    //    自定义判断登录成功的manager交给工厂进行管理
    @Bean
    public AuthenticationManager myAuthenticationManager(AuthenticationConfiguration authenticationConfiguration) {
        try {
            return authenticationConfiguration.getAuthenticationManager();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //    自定义filter交给工厂进行管理
    @Bean
    public LoginFilter loginFilter() {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin"); // 指定认证的url
        loginFilter.setUsernameParameter("uname"); // 指定接收json的用户名密码 这里进行了设置，自定义的LoginFilter中才能通过getUsernameParameter获得相应的用户名
        loginFilter.setPasswordParameter("passwd");
//        loginFilter.setAuthenticationManager(new CustomerAuthenticationManager());  // // 如果直接定义数据源使用这行，似乎没有起到作用，这种情况下，使用blr也能登录成功。
        loginFilter.setRememberMeServices(rememberMeServices());  // 设置认证成功时，使用自定义的rememberMeServices
        loginFilter.setAuthenticationManager(myAuthenticationManager(authenticationConfiguration)); // 使用它进行认证
        // 认证成功处理
        loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());
//        认证失败处理
        loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
        return loginFilter;
    }

    //    自动注入：不知道为什么使用这种方式注入userDetailsService，则自动登录会失败
    @Autowired
    public void initialized(AuthenticationManagerBuilder builder) throws Exception {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
        builder.userDetailsService(userDetailsManager);

//        builder.userDetailsService(myUserDetailService);
    }

    //        方法2、持久化令牌存储到数据库中
    @Bean
    public PersistentTokenRepository myPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setCreateTableOnStartup(false);  //第一次登录时设置为true，之后需要手动设置为false
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

//    注入自定义的RememberMeServices：通过内存的方式实现
    @Bean
    public RememberMeServices rememberMeServices() {
//        方法一：直接通过内存存储令牌token
//        return new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, new InMemoryTokenRepositoryImpl());
//        方法二：通过数据库存储认证生成的令牌token
        return new MyPersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(), myUserDetailService, myPersistentTokenRepository());
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> {
                    authorizationManagerRequestMatcherRegistry
//                            .requestMatchers("/doLogin").permitAll()
                            .requestMatchers("/index").permitAll()
                            .anyRequest().authenticated();
                })
                .formLogin(formLogin -> {
//                    formLogin.passwordParameter("passwd")
//                            .usernameParameter("uname");
                })
                //   在前后端分离中，如果访问了权限保护的资源，则进行提醒
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer -> {
                    httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint((request, response, authException) -> {
                        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.getWriter().println("必须认证以后才能进行访问！");
                    });
                })
                .logout(httpSecurityLogoutConfigurer -> {
                    httpSecurityLogoutConfigurer
                            .logoutRequestMatcher(new OrRequestMatcher(
                                    new AntPathRequestMatcher("/logout", HttpMethod.GET.name()),
                                    new AntPathRequestMatcher("/logout", HttpMethod.DELETE.name())
                            ))
                            .invalidateHttpSession(true)
                            .clearAuthentication(true)
                            .logoutSuccessHandler(new MyLogoutSuccessHandler());
                })
                .addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class)  // 使用自定义的filter进行认证
//                开启记住我功能 cookie实现 1、进行认证成功后保存记住我 cookie到客户端  2、只有cookie写入到了客户端且成功才能实现自动登录功能
//                TODO：开启记住我后，如果是将token存储到内存中，则可访问/test，如果是存储到数据库中，则访问不了，是否和认证成功了也无法访问/test有关？？？？
                .rememberMe(
                        httpSecurityRememberMeConfigurer -> {
                    httpSecurityRememberMeConfigurer
                            .rememberMeServices(rememberMeServices())  // 自定义rememberServices，设置会话过期且携带cookie登录时，自动登录使用，
                     .tokenValiditySeconds(86400); // 有效时间（默认为14天，这里设置为）
                })
                .csrf(httpSecurityCsrfConfigurer -> {
                    httpSecurityCsrfConfigurer.disable();
                }).build();
    }
}
